package com.bluexmicro.android.ota

import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.content.Context
import android.os.Build
import android.util.Log
import com.bluexmicro.android.coroutineble.BluetoothPeripheral
import com.bluexmicro.android.coroutineble.model.ConnectionState
import com.bluexmicro.android.ota.TransactionState.*
import com.bluexmicro.android.ota.entity.Constant
import com.bluexmicro.android.ota.entity.OtaTask
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withTimeout
import java.util.*
import kotlin.jvm.Throws

class CoroutineOta(
    context: Context,
    device: BluetoothDevice,
    private val serviceUUID: UUID = Constant.UUID_BX_DEFAULT_OTA_SERVICE,
    private val ctrlUUID: UUID = Constant.UUID_BX_DEFAULT_CTRL,
    private val dataUUID: UUID = Constant.UUID_BX_DEFAULT_DATA
) {

    private var _ctrl: BluetoothGattCharacteristic? = null
    private var _data: BluetoothGattCharacteristic? = null
    private val peripheral: BluetoothPeripheral = BluetoothPeripheral(context, device)
    private var mtuSize = 23
    private var pendingSegmentSize = 23 //19+1+3
    private val transactionState = MutableStateFlow(IDLE)

    ///////////////////////////////////////////////////////////////////////////
    // 蓝牙基础操作
    ///////////////////////////////////////////////////////////////////////////

    @Throws(IllegalStateException::class)
    private suspend fun connect() {
        val phy = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O) {
            //ViVO和Oppo的手机无法使用Phy接口登录
            BluetoothDevice.PHY_LE_2M_MASK or BluetoothDevice.PHY_LE_1M_MASK or BluetoothDevice.PHY_LE_CODED_MASK
        } else {
            null
        }
        var result = try {
            withTimeout(5_000) {
                peripheral.connect(phy)
            }
        } catch (e: Exception) {
            peripheral.disconnect()
            delay(1000)
            false
        }
        if (result) return
        for (i in 0..1) {
//            result = peripheral.connect(null)
//            if (result) return
            result = try {
                withTimeout(5_000) {
                    peripheral.connect(null)
                }
            } catch (e: Exception) {
                peripheral.disconnect()
                delay(1000)
                false
            }
            if (result) return
        }
        transactionState.value = IDLE
        throw IllegalStateException("Connect Failed!")
    }

    @Throws(IllegalStateException::class)
    private fun checkOtaService() {
        val errorMsg = "This device does not support BlueX OTA!"
        val otaService =
            peripheral.findService(serviceUUID) ?: throw IllegalStateException(errorMsg)
        val ctrl =
            otaService.getCharacteristic(ctrlUUID) ?: throw IllegalStateException(errorMsg)
        val data =
            otaService.getCharacteristic(dataUUID) ?: throw IllegalStateException(errorMsg)
        val flag1 = ctrl.properties and Constant.CTRL_REQUIRED_PROPERTIES > 0
        val flag2 = data.properties and Constant.DATA_REQUIRED_PROPERTIES > 0
        if (flag1 && flag2) {
            _ctrl = ctrl
            _data = data
        } else {
            throw IllegalStateException(errorMsg)
        }
    }

    @Throws(IllegalStateException::class)
    private suspend fun setupIndicate() {
        if (_ctrl == null || !peripheral.toggleNotification(_ctrl!!, true)) {
            throw IllegalStateException("Enable Indicate Failed!")
        }
    }

    fun release() {
        peripheral.release()
    }

    ///////////////////////////////////////////////////////////////////////////
    // 事务
    ///////////////////////////////////////////////////////////////////////////


    @Throws(IllegalStateException::class)
    suspend fun startTransaction(fastMode: Boolean) {
        transactionState.value = OPENING
        connect()
        peripheral.discoverServices()
        try {
            checkOtaService()
            setupIndicate()
        } catch (e: Exception) {
            transactionState.value = IDLE
            peripheral.disconnect()
            throw e
        }
        if (fastMode) {
            mtuSize = peripheral.requestMtu(517) ?: 23
            pendingSegmentSize = mtuSize
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            peripheral.requestPhy()
        }
//        if (mtuSize >= 33) pendingSegmentSize -= 10
        pendingSegmentSize = mtuSize.coerceAtMost(200)
        transactionState.value = RUNNING
        Log.d("OTA", "pendingSegmentSize: $pendingSegmentSize")
    }

    fun execute(task: OtaTask, fastMode: Boolean): Flow<Float> = flow {
        checkOrWait(fastMode)
        val basicProcess = BasicProcess(peripheral, task, _ctrl!!, _data!!, pendingSegmentSize, fastMode)
        delay(2000)
        basicProcess.execute().collect {
            emit(it)
        }
    }

    private suspend fun checkOrWait(fastMode: Boolean) {
        when (transactionState.value) {
            IDLE -> startTransaction(fastMode)
            OPENING -> transactionState.first {
                it == RUNNING
            }
            RUNNING -> {
                Log.e("TAG", "checkOrWait: ${peripheral.connectionState.value.name}")
                if (peripheral.connectionState.value != ConnectionState.CONNECTED) {
                    transactionState.value = IDLE
                    startTransaction(fastMode)
                }
            }
        }
    }

    suspend fun endTransaction(fastMode: Boolean) {
        if (peripheral.connectionState.value == ConnectionState.CONNECTED && _ctrl != null && fastMode) {
            peripheral.write(_ctrl!!, byteArrayOf(0x0B))
        }
        transactionState.value = IDLE
        peripheral.release()
    }

    fun reset(){
        transactionState.value = IDLE
        peripheral.disconnect()
    }

}

enum class TransactionState {
    IDLE, OPENING, RUNNING
}